<script lang="tsx">
  import type { Ref } from 'vue'
  import { defineComponent, ref, computed, unref, reactive, watch, watchEffect } from 'vue'
  import { useTimeoutFn } from '/@/hooks/core/useTimeout'
  import { useEventListener } from '/@/hooks/event/useEventListener'
  import { basicProps } from './props'
  import { getSlot } from '/@/utils/helper/tsxHelper'
  import { CheckOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'

  export default defineComponent({
    name: 'BaseDargVerify',
    props: basicProps,
    emits: ['success', 'update:value', 'change', 'start', 'move', 'end'],
    setup(props, { emit, slots, expose }) {
      const state = reactive({
        isMoving: false,
        isPassing: false,
        moveDistance: 0,
        toLeft: false,
        startTime: 0,
        endTime: 0,
      })

      const wrapElRef = ref<HTMLDivElement | null>(null)
      const barElRef = ref<HTMLDivElement | null>(null)
      const contentElRef = ref<HTMLDivElement | null>(null)
      const actionElRef = ref(null) as Ref<HTMLDivElement | null>

      useEventListener({
        el: document,
        name: 'mouseup',
        listener: () => {
          if (state.isMoving) {
            resume()
          }
        },
      })

      const getActionStyleRef = computed(() => {
        const { height, actionStyle } = props
        const h = `${parseInt(height as string)}px`
        return {
          left: 0,
          width: h,
          height: h,
          ...actionStyle,
        }
      })

      const getWrapStyleRef = computed(() => {
        const { height, width, circle, wrapStyle } = props
        const h = parseInt(height as string)
        const w = `${parseInt(width as string)}px`
        return {
          width: w,
          height: `${h}px`,
          lineHeight: `${h}px`,
          borderRadius: circle ? h / 2 + 'px' : 0,
          ...wrapStyle,
        }
      })

      const getBarStyleRef = computed(() => {
        const { height, circle, barStyle } = props
        const h = parseInt(height as string)
        return {
          height: `${h}px`,
          borderRadius: circle ? h / 2 + 'px 0 0 ' + h / 2 + 'px' : 0,
          ...barStyle,
        }
      })

      const getContentStyleRef = computed(() => {
        const { height, width, contentStyle } = props
        const h = `${parseInt(height as string)}px`
        const w = `${parseInt(width as string)}px`

        return {
          height: h,
          width: w,
          ...contentStyle,
        }
      })

      watch(
        () => state.isPassing,
        (isPassing) => {
          if (isPassing) {
            const { startTime, endTime } = state
            const time = (endTime - startTime) / 1000
            emit('success', { isPassing, time: time.toFixed(1) })
            emit('update:value', isPassing)
            emit('change', isPassing)
          }
        },
      )

      watchEffect(() => {
        state.isPassing = !!props.value
      })

      function getEventPageX(e: MouseEvent | TouchEvent) {
        return (e as MouseEvent).pageX || (e as TouchEvent).touches[0].pageX
      }

      function handleDragStart(e: MouseEvent | TouchEvent) {
        if (state.isPassing) {
          return
        }
        const actionEl = unref(actionElRef)
        if (!actionEl) return
        emit('start', e)
        state.moveDistance = getEventPageX(e) - parseInt(actionEl.style.left.replace('px', ''), 10)
        state.startTime = new Date().getTime()
        state.isMoving = true
      }

      function getOffset(el: HTMLDivElement) {
        const actionWidth = parseInt(el.style.width)
        const { width } = props
        const widthNum = parseInt(width as string)
        const offset = widthNum - actionWidth - 6
        return { offset, widthNum, actionWidth }
      }

      function handleDragMoving(e: MouseEvent | TouchEvent) {
        const { isMoving, moveDistance } = state
        if (isMoving) {
          const actionEl = unref(actionElRef)
          const barEl = unref(barElRef)
          if (!actionEl || !barEl) return
          const { offset, widthNum, actionWidth } = getOffset(actionEl)
          const moveX = getEventPageX(e) - moveDistance

          emit('move', {
            event: e,
            moveDistance,
            moveX,
          })
          if (moveX > 0 && moveX <= offset) {
            actionEl.style.left = `${moveX}px`
            barEl.style.width = `${moveX + actionWidth / 2}px`
          } else if (moveX > offset) {
            actionEl.style.left = `${widthNum - actionWidth}px`
            barEl.style.width = `${widthNum - actionWidth / 2}px`
            if (!props.isSlot) {
              checkPass()
            }
          }
        }
      }

      function handleDragOver(e: MouseEvent | TouchEvent) {
        const { isMoving, isPassing, moveDistance } = state
        if (isMoving && !isPassing) {
          emit('end', e)
          const actionEl = unref(actionElRef)
          const barEl = unref(barElRef)
          if (!actionEl || !barEl) return
          const moveX = getEventPageX(e) - moveDistance
          const { offset, widthNum, actionWidth } = getOffset(actionEl)
          if (moveX < offset) {
            if (!props.isSlot) {
              resume()
            } else {
              setTimeout(() => {
                if (!props.value) {
                  resume()
                } else {
                  const contentEl = unref(contentElRef)
                  if (contentEl) {
                    contentEl.style.width = `${parseInt(barEl.style.width)}px`
                  }
                }
              }, 0)
            }
          } else {
            actionEl.style.left = `${widthNum - actionWidth}px`
            barEl.style.width = `${widthNum - actionWidth / 2}px`
            checkPass()
          }
          state.isMoving = false
        }
      }

      function checkPass() {
        if (props.isSlot) {
          resume()
          return
        }
        state.endTime = new Date().getTime()
        state.isPassing = true
        state.isMoving = false
      }

      function resume() {
        state.isMoving = false
        state.isPassing = false
        state.moveDistance = 0
        state.toLeft = false
        state.startTime = 0
        state.endTime = 0
        const actionEl = unref(actionElRef)
        const barEl = unref(barElRef)
        const contentEl = unref(contentElRef)
        if (!actionEl || !barEl || !contentEl) return
        state.toLeft = true
        useTimeoutFn(() => {
          state.toLeft = false
          actionEl.style.left = '0'
          barEl.style.width = '0'
          //  The time is consistent with the animation time
        }, 300)
        contentEl.style.width = unref(getContentStyleRef).width
      }

      expose({
        resume,
      })

      return () => {
        const renderBar = () => {
          const cls = [`darg-verify-bar`]
          if (state.toLeft) {
            cls.push('to-left')
          }
          return <div class={cls} ref={barElRef} style={unref(getBarStyleRef)} />
        }

        const renderContent = () => {
          const cls = [`darg-verify-content`]
          const { isPassing } = state
          const { text, successText } = props

          isPassing && cls.push('success')

          return (
            <div class={cls} ref={contentElRef} style={unref(getContentStyleRef)}>
              {getSlot(slots, 'text', isPassing) || (isPassing ? successText : text)}
            </div>
          )
        }

        const renderAction = () => {
          const cls = [`darg-verify-action`]
          const { toLeft, isPassing } = state
          if (toLeft) {
            cls.push('to-left')
          }
          return (
            <div
              class={cls}
              onMousedown={handleDragStart}
              onTouchstart={handleDragStart}
              style={unref(getActionStyleRef)}
              ref={actionElRef}
            >
              {getSlot(slots, 'actionIcon', isPassing) ||
                (isPassing ? (
                  <CheckOutlined class={`darg-verify-action__icon`} />
                ) : (
                  <DoubleRightOutlined class={`darg-verify-action__icon`} />
                ))}
            </div>
          )
        }

        return (
          <div
            class="darg-verify"
            ref={wrapElRef}
            style={unref(getWrapStyleRef)}
            onMousemove={handleDragMoving}
            onTouchmove={handleDragMoving}
            onMouseleave={handleDragOver}
            onMouseup={handleDragOver}
            onTouchend={handleDragOver}
          >
            {renderBar()}
            {renderContent()}
            {renderAction()}
          </div>
        )
      }
    },
  })
</script>
<style lang="less">
  @radius: 4px;

  .darg-verify {
    position: relative;
    overflow: hidden;
    text-align: center;
    background-color: rgb(238 238 238);
    border: 1px solid #ddd;
    border-radius: @radius;

    &-bar {
      position: absolute;
      width: 0;
      height: 36px;
      background-color: @success-color;
      border-radius: @radius;

      &.to-left {
        width: 0 !important;
        transition: width 0.3s;
      }
    }

    &-content {
      position: absolute;
      top: 0;
      font-size: 12px;
      text-size-adjust: none;
      background-color: -webkit-gradient(
        linear,
        left top,
        right top,
        color-stop(0, #333),
        color-stop(0.4, #333),
        color-stop(0.5, #fff),
        color-stop(0.6, #333),
        color-stop(1, #333)
      );
      animation: slidetounlock 3s infinite;
      background-clip: text;
      user-select: none;

      &.success {
        -webkit-text-fill-color: @white;
      }

      & > * {
        -webkit-text-fill-color: #333;
      }
    }

    &-action {
      position: absolute;
      top: 0;
      left: 0;
      display: flex;
      cursor: move;
      background-color: @white;
      border-radius: @radius;
      justify-content: center;
      align-items: center;

      &__icon {
        cursor: inherit;
      }

      &.to-left {
        left: 0 !important;
        transition: left 0.3s;
      }
    }
  }

  @keyframes slidetounlock {
    0% {
      background-position: -120px 0;
    }

    100% {
      background-position: 120px 0;
    }
  }
</style>
